﻿using System;
using System.Collections.Generic;
using System.Text;

namespace Vortex.Drawing {
	/// <summary>
	/// Sprite is a logical representation of textured area. It combines reference to texture and texture coordinates.
	/// Sprite is immutable. It means you can't modify it since it is created. All of modification methods returns copy of sprite with adjusted parameters.
	/// Any sprite can be splitted or cutted. See Split and Cut methods references.
	/// </summary>
	public class Sprite : ISpriteSource {

		protected BaseTexture _texture;
		protected Rect _transformedRegion;
		protected Rect _region;
		protected Vector2 _size;
		protected Vector2 _origin;

		#region Constructor

		/// <summary>
		/// Initializes a new instance of the <see cref="Sprite"/> class.
		/// </summary>
		/// <param name="sprite">The sprite to copy properties.</param>
		public Sprite(Sprite sprite) {
			_texture = sprite.Texture;
			_transformedRegion = sprite._transformedRegion;
			_region = sprite._region;
			_size = sprite._size;
			_origin = sprite._origin;
		}

		/// <summary>
		/// Initializes a new instance of the <see cref="Sprite"/> class as entire texture texture.
		/// </summary>
		/// <param name="texture">The texture instance.</param>
		public Sprite(BaseTexture texture) {
			if (null == texture) throw new ArgumentNullException("texture");

			_texture = texture;
			_size = (Vector2)texture.Size;
			_origin = _size.Half;

			_transformedRegion.LeftTop = Vector2.Zero;
			_transformedRegion.RightBottom = Vector2.Unit;

			_region.LeftTop = Vector2.Zero;
			_region.RightBottom = _size;
		}

		/// <summary>
		/// Initializes a new instance of the <see cref="Sprite"/> class as a part of texture.
		/// </summary>
		/// <param name="texture">The texture instance.</param>
		/// <param name="region">The region of texture in pixel units.</param>
		public Sprite(BaseTexture texture, Rect region) {
			if (null == texture) throw new ArgumentNullException("texture");

			_texture = texture;
			_size = region.Size;
			_origin = _size.Half;

			_region = region;
			texture.Transform(ref region, out _transformedRegion);
		}

		/// <summary>
		/// Initializes a new instance of the <see cref="Sprite"/> class as a part of texture.
		/// </summary>
		/// <param name="texture">The texture instance.</param>
		/// <param name="x">The x coord of left top sprite point.</param>
		/// <param name="y">The y coord of left top sprite point.</param>
		/// <param name="width">The width of sprite.</param>
		/// <param name="height">The height of sprite.</param>
		public Sprite(BaseTexture texture, float x, float y, float width, float height)
			: this(texture, new Rect(x, y, x + width, y + height)) {
		}

		#endregion

		#region Properties

		/// <summary>
		/// Gets the original sprite region.
		/// </summary>
		/// <value>The region.</value>
		public Rect Region {
			get { return _region; }
		}

		/// <summary>
		/// Gets the transformed sprite region.
		/// </summary>
		/// <value>The transformed sprite region.</value>
		public Rect TransformedRegion {
			get { return _transformedRegion; }
		}

		/// <summary>
		/// Gets the texture for this sprite.
		/// </summary>
		/// <value>The texture instance.</value>
		public BaseTexture Texture {
			get { return _texture; }
		}

		/// <summary>
		/// Gets the origin point offset relative to sprite left top point.
		/// </summary>
		/// <value>The sprite origin offset relative to sprite left top point.</value>
		public Vector2 Origin {
			get { return _origin; }
		}

		/// <summary>
		/// Gets the original size of sprite. It is size of region in texture pixels.
		/// </summary>
		/// <value>The original size of sprite.</value>
		public Vector2 OriginalSize {
			get { return _region.Size; }
		}

		/// <summary>
		/// Gets the size of sprite (originally it is pixel-to-texel mapped coordinates).
		/// </summary>
		/// <value>The size of sprite.</value>
		public Vector2 Size {
			get { return _size; }
		}

		#endregion

		#region Construction Methods

		/// <summary>
		/// Cuts a new sub sprite from this one. Note, overflow is not exceptional situation.
		/// </summary>
		/// <param name="subRegion">The region inside of sprite.</param>
		/// <returns><see cref="Vortex.Drawing.Sprite"/> cutted from this one</returns>
		public Sprite Cut(Rect subRegion) {
			return new Sprite(Texture, subRegion + _region.LeftTop);
		}

		/// <summary>
		/// Cuts a new sub sprite from this one. Note, overflow is not exceptional situation
		/// </summary>
		/// <param name="offsetX">The X offset inside of sprite.</param>
		/// <param name="offsetY">The Y offset inside of sprite.</param>
		/// <param name="width">The width of new sprite.</param>
		/// <param name="height">The height of new sprite.</param>
		/// <returns><see cref="Vortex.Drawing.Sprite"/> cutted from this one</returns>
		public Sprite Cut(int offsetX, int offsetY, int width, int height) {
			return new Sprite(
				Texture, Rect.FromBox(_region.Left + offsetX, _region.Top + offsetY, width, height)
			);
		}

		/// <summary>
		/// Cuts the specified sprite without frame around.
		/// </summary>
		/// <param name="frameWidth">Width of the frame.</param>
		/// <param name="frameHeight">Height of the frame.</param>
		/// <returns><see cref="Vortex.Drawing.Sprite"/> inside current one but less</returns>
		public Sprite CutFrame(float frameWidth, float frameHeight) {
			return new Sprite(
				Texture, Region.Deflate(frameWidth, frameHeight)
			);
		}

		/// <summary>
		/// Create new sprite with the specified size.
		/// </summary>
		/// <param name="newSize">The new size for sprite.</param>
		/// <returns>The copy of this <see cref="Sprite"/> with new size</returns>
		public Sprite Resize(Vector2 newSize) {
			if (newSize.IsEmpty) throw new ArgumentException("Sprite size can't be empty", "newSize");

			Sprite sprite = new Sprite(this);
			sprite._origin *= newSize / sprite._size;	//need to scale origin appropriatelly
			sprite._size = newSize;

			return sprite;
		}

		/// <summary>
		/// Creates new sprite with the specified size. Note, chaning size origin point is scaled automatically
		/// </summary>
		/// <param name="width">The new sprite width.</param>
		/// <param name="height">The new sprite height.</param>
		/// <returns>
		/// The copy of this <see cref="Sprite"/> with new size
		/// </returns>
		public Sprite Resize(float width, float height) {
			return Resize(new Vector2(width, height));
		}

		/// <summary>
		/// Creates new sprite with specified origin point offset. Note, chaning size origin point is scaled automatically
		/// </summary>
		/// <param name="relativeCenterOffset">The offset relative to sprite top left point.</param>
		/// <returns>The copy of this <see cref="Sprite"/> with new origin point</returns>
		public Sprite MoveOriginTo(Vector2 topLeftOffset) {
			Sprite sprite = new Sprite(this);
			sprite._origin = topLeftOffset;
			return sprite;
		}

		/// <summary>
		/// Creates new sprite with specified origin point offset
		/// </summary>
		/// <param name="relCenterX">The offset X relative to left top point of sprite.</param>
		/// <param name="relCenterY">The offset Y relative to left top point of sprite.</param>
		/// <returns>
		/// The copy of this <see cref="Sprite"/> with new origin point
		/// </returns>
		public Sprite MoveOriginTo(float offsetLeft, float offsetTop) {
			return MoveOriginTo(new Vector2(offsetLeft, offsetTop));
		}

		/// <summary>
		/// Moves the sprite origin to center of sprite.
		/// </summary>
		/// <returns>The copy of this <see cref="Sprite"/> with origin point at the center of sprite</returns>
		public Sprite MoveOriginToCenter() {
			return this.MoveOriginTo(_size.Half);
		}

		#endregion

		#region ISpriteProvider Members

		/// <summary>
		/// Returns this sprite (just supports ISpriteProvider model). To create copy use Clone() method.
		/// </summary>
		/// <returns><see cref="Sprite"/> instance.</returns>
		public Sprite ToSprite() {
			return this;
		}

		#endregion

		/// <summary>
		/// Splits the specified sprite into grid.
		/// </summary>
		/// <param name="gridWidth">Width of the sprite grid.</param>
		/// <param name="gridHeight">Height of the sprite grid.</param>
		/// <returns>Resulted sprite grid</returns>
		public SpriteGrid Split(int gridWidth, int gridHeight) {
			return new SpriteGrid(this, gridWidth, gridHeight);
		}

		/// <summary>
		/// Splits sprite vertically.
		/// </summary>
		/// <param name="gridHeight">Height of the result sprite grid.</param>
		/// <returns>Sprite grid</returns>
		public SpriteGrid SplitVertical(int gridHeight) {
			return Split(1, gridHeight);
		}

		/// <summary>
		/// Splits sprite horizontally.
		/// </summary>
		/// <param name="gridWidth">Width of the result sprite grid.</param>
		/// <returns>Sprite grid</returns>
		public SpriteGrid SplitHorizontal(int gridWidth) {
			return Split(gridWidth, 1);
		}

		/// <summary>
		/// Flips sprite vertically.
		/// </summary>
		/// <returns>Flipped sprite</returns>
		public Sprite FlipVertical() {
			Sprite sprite = new Sprite(this);
			sprite._transformedRegion = sprite._transformedRegion.FlipVertical();
			return sprite;
		}

		/// <summary>
		/// Flips sprite horizontally.
		/// </summary>
		/// <returns>Flipped sprite</returns>
		public Sprite FlipHorizontal() {
			Sprite sprite = new Sprite(this);
			sprite._transformedRegion = sprite._transformedRegion.FlipHorizontal();
			return sprite;
		}
	}
}
